const config = require('config')
const redis = require('redis');
const { password, port, host } = config.get('redis');

const { getLogger } = require('../utils');
const logger = getLogger(__filename);

class RedisPubSub {
	constructor() {
		const redis_conf = {
			port, host, retry_strategy: RedisPubSub._redisRetry
		}
		const redisClient = redis.createClient(redis_conf);

		// redisClient.psubscribe("clf:agora:from-html5");

		redisClient.on("connect", () => {
			logger.info('redis client connect!');
		});

		redisClient.on("error", (error) => {
			logger.error("redis client error.", error);
		});

		redisClient.on("reconnecting", (msg) => {
			logger.warn(`redis client reconnecting!${JSON.stringify(msg)}`);
		});

		redisClient.on("psubscribe", (channel, count) => {
			logger.info("Successfully subscribed to pattern [" + channel + "]");
		});

		redisClient.on("pmessage", (pattern, channel, _message) => {
			let msg = (typeof _message !== 'object') ? JSON.parse(_message) : _message;
			redisClient.emit('cmessage', msg);
		});
		this.pub = redis.createClient(redis_conf);
		this.sub = redisClient;
	}

	on(...args) {
		this.sub.on(...args);
	}

	async getUID(meetingId) {
		return new Promise((resolve, reject) => {
			const EXPIRE_TIME = 86400;
			let incrKey = `agora:nextRecordUID:${meetingId}`;
			this.pub.incr(incrKey, (err, msgId) => {
				if (err) {
					return reject(err);
				}
				resolve(msgId);
				if (msgId === 1) {
					this.pub.expire(incrKey, EXPIRE_TIME);
				}
			});
		})
	}

	async deleteUID(meetingId) {
		return new Promise((resolve, reject) => {
			let incrKey = `agora:nextRecordUID:${meetingId}`;
			this.pub.del(incrKey, (err, ret) => {
				if (err) {
					reject(err);
				} else {
					resolve(ret);
				}
			});
		})

	}

	blowObject(obj) {
		let arr = [];
		Object.keys(obj).map(function(key) {
			if (obj[key] == null || obj[key] === undefined) {
				return;
			}
			arr.push(key);
			arr.push(obj[key]);
		});
		return arr;
	};

	setMeetingInfo(key, uid, obj) {
		if (obj) {
			key = this.createKey(key, uid)
		} else {
			obj = uid
		}
		return new Promise((resolve, reject) => {
			const EXPIRE_TIME = 86400; // 1天
			let skey = `agora:records:all`;
			let value = this.blowObject(obj);
			this.pub.multi()
				.hmset(key, value)
				.expire(key, EXPIRE_TIME)
				.sadd(skey, key)
				.expire(skey, EXPIRE_TIME)
				.exec((err, replies) => {
					// console.log('replies',replies);
					if (err) {
						return reject(err);
					}
					resolve(replies);
				})
		});
	}

	createKey(meetingId, uid) {
		return `agora:recordInfo:${meetingId}:${uid}`;
	}

	getMeetingInfo(key, uid) {
		key = uid ? this.createKey(key, uid) : key
		return new Promise((resolve, reject) => {
			this.pub.hgetall(key, (err, res) => {
				if (err) {
					return reject(err);
				}
				resolve(res);
			});
		});
	}

	deleteMeetingInfo(key, uid) {
		key = uid ? this.createKey(key, uid) : key
		return new Promise((resolve, reject) => {
			this.pub.multi()
				.del(key)
				.srem('agora:records:all', key)
				.exec((err, replies) => {
					// console.log('replies',replies);
					if (err) {
						return reject(err);
					}
					resolve(replies);
				})
		})
	}

	getAllRecords() {
		return new Promise((resolve, reject) => {
			let skey = `agora:records:all`;
			this.pub.smembers(skey, (err, res) => {
				if (err) {
					return reject(err);
				}
				resolve(res);
			});
		});
	}


	writeMeetingEvent(meetingId, message) {
		return new Promise((resolve, reject) => {
			const EXPIRE_TIME = 1209600;
			let recKey = 'recording:' + meetingId;

			let blowObject = function(obj) {
				let arr = [];
				Object.keys(obj).map(function(key) {
					arr.push(key);
					arr.push(obj[key]);
				});
				return arr;
			};

			this.pub.incr('global:nextRecordedMsgId', (err, msgId) => {
				if (err) {
					return reject(err);
				}

				let incr = recKey + ':' + msgId;
				let recordKey = 'meeting:' + meetingId + ':recordings';
				let value = blowObject(message);
				this.pub
					.multi()
					.hmset(incr, value)
					.expire(incr, EXPIRE_TIME)
					.rpush(recordKey, msgId)
					.expire(recordKey, EXPIRE_TIME)
					.exec((err, replies) => {
						if (err) {
							return reject(err);
						}
						resolve(replies.concat([recordKey, incr]))
					})
			});
		})
	}

	modifyMeetingEventFields(key, ...args) {
		return new Promise(function(resolve, reject) {
			this.pub.hmset(key, ...args, (err, rts) => {
				if (err) {
					return reject(err);
				}
				resolve(rts)
			})
		})
	}

	getMeetingEventFields(key, ...args) {
		return new Promise(function(resolve, reject) {
			this.pub.hmget(key, ...args, (err, rts) => {
				if (err) {
					return reject(err);
				}
				resolve(rts)
			})
		})
	}

	static _redisRetry(options) {
		// if (options.error && options.error.code === 'ECONNREFUSED') {
		//   return new Error('The server refused the connection');
		// }
		if (options.total_retry_time > RedisPubSub._retryThreshold) {
			return new Error('Retry time exhausted');
		}
		if (options.times_connected > RedisPubSub._maxRetries) {
			return undefined;
		}
		return Math.max(options.attempt * 100, 3000);
	}

	static get _retryThreshold() {
		return 1000 * 60 * 60;
	}

	static get _maxRetries() {
		return 10;
	}
}

module.exports = new RedisPubSub();
